
package com.workday.gridsimulator

import akka.actor.Actor
import akka.actor.ActorRef
import akka.event.Logging
import akka.dispatch.Await
import akka.pattern.{ ask, pipe }
import akka.util.duration._
import akka.util.Timeout

/**
 * Clock actor.
 * 
 * Manages the agenda of work to be done (by other actors) and the overall current time of the simulation
 * 
 * Stops running when the agenda is empty and all actors associated with the simulation have notified the Clock that they are finished
 * 
 */
class Clock extends Actor {

  private var currentTime = 0.0
  private var running = false
  private var actors: Set[ActorRef] = Set.empty
  private var agenda: List[WorkItem] = List();
  private val log = Logging(context.system.eventStream, self.path.toString())

  /**
	 * Adds an actor to the simulation
	 * 
	 * @param actor the actor to add
	 *            
	 */
  def add(actor: ActorRef) = {
    if (!actors.contains(actor)) {
      actors += actor
      log.info("added " + actor)
      if (running) {
        actor ! Start(currentTime)
        log.info(actor + " was told to start during a running simulation")
      }
    }
  }

  /**
   * Implements the akka actor receive method
   * Uses a case class for each possible message the Clock may receive
   */
  override def receive = {
    case Start(currentTime) =>
      log.info("is starting at: " + currentTime)
      this.currentTime = currentTime
      begin
      
    case Stop =>
      log.info("told to stop by: " + sender)
      stop
      
    case Hold(arrivalTime, actorRef) =>
      log.info("inserted workitem into agenda at " + arrivalTime + " for " + actorRef)
      agenda = insert(agenda, WorkItem(arrivalTime, actorRef))
      advanceClock
      
    case Finished(actorRef) =>
      log.info("actor: " + actorRef + " has finished ")
      //for(w <- actors) println(w)
      actors -= actorRef
      advanceClock
      
    case AddMe(actorRef) =>
      log.info("is adding " + actorRef.path)
      add(actorRef)
      
    case WhatTimeIsIt =>
      sender ! currentTime
      
    case IsClockRunning =>
      sender ! running
      
    case ActorCount =>
      sender ! actors.size
      
    case _ =>
      log.warning("received an unexpected message from " + sender)
  }

  /**
	 * Begins the simulation, tells the known simulation actors to start
	 */
  private def begin = {
    running = true
    for (actor <- actors) {
      actor ! Start(currentTime)
      log.info("told " + actor + " to start ")
    }
  }

  /**
	 * Stops the simulation, tell any actors still associated with the Clock to stop
	 * Resets the agenda and simulation actors list so we can run a fresh simulation if we want to
	 */
  private def stop = {
    running = false
    log.info("is stopping at: " + currentTime)
    for (actor <- actors) {
      log.info("telling " + actor + " to stop")
      actor ! Stop(currentTime)
    }
    actors = Set.empty
    agenda = List()
  }

  /**
	 * Inserts a workitem into the end of the agenda
	 * 
	 * @param currentAgenda the current agenda of work
	 *            
	 * @param newWork the work to insert
	 *            
	 * @return List the new agenda which contains the new work item
	 */
  private def insert(currentAgenda: List[WorkItem], newWork: WorkItem): List[WorkItem] = {
    if (currentAgenda.isEmpty) newWork :: List()
    //else newWork :: currentAgenda //new list with newWork followed by currentAgenda
    else currentAgenda ::: List(newWork)	//adds the new work item to the end of the list
  }

  /**
	 * Advance clock.
	 */
  private def advanceClock = {

    if (!agenda.isEmpty) {
      val work = agenda.head
      agenda = agenda.tail
      currentTime = work.time;
      // Update waiting actor with new time
      log.info("pinging " + work.actor + " at " + currentTime)
      work.actor ! Ping(currentTime)
    } else if (actors.isEmpty && running != false) {
      log.info("about to stop")
      self ! Stop
    }
  }
}